﻿// Supervisor: HEB
// Part of Helios Green, proprietary software, (c) LCS International, a. s.
// Redistribution and use in source and binary forms, with or without modification, 
// is not permitted without valid contract with LCS International, a. s.

//------------------------------------------------------------------
//IMPORTANT NOTE - this file is also part of project MakePass. (HEB)
// and SourceSafeUtility (by JR).
//DO NOT CHANGE THIS FILE - CONTACT HEB@LCS.CZ
//------------------------------------------------------------------

//HEB Crypto is kept due compatibility issues. See Cryptography.cs

using System;
using System.Security.Cryptography;

namespace Noris.Srv
{

	/// <summary>
	/// Noris Cryptography class
	/// </summary>
	public static class Crypto
	{
		/// <summary>
		/// Vytvoření hash klíče šifrovací metodou MD5
		/// </summary>
		/// <param name="data">Data podle kterých se má vytvořit hash</param>
		/// <returns>Vytvořený hash</returns>
		public static byte[] MD5Hash(byte[] data)
		{
			MD5 md5 = new MD5CryptoServiceProvider();
			byte[] result = md5.ComputeHash(data);
			return result;
		}
		/// <summary>
		/// Vytvoření hash klíče šifrovací metodou MD5
		/// </summary>
		/// <param name="data">Data podle kterých se má vytvořit hash</param>
		/// <param name="base64">Vrátit hash v kodování Base64</param>
		/// <returns>Vytvořený hash</returns>
		public static string MD5Hash(string data, bool base64)
		{
			byte[] txt = System.Text.Encoding.Default.GetBytes(data);
			byte[] hash = Crypto.MD5Hash(txt);

			if (base64)
			{
				return Convert.ToBase64String(hash);
			}
			return System.Text.Encoding.ASCII.GetString(hash);
		}

		//this cryptoprovider is used to save keep sensitive information between client and server.
		//each server restart changes this.
		//public static RSACryptoServiceProvider CommProtocol = new RSACryptoServiceProvider();
		private static bool UseRSA = false;

		const string CONFIG_ERROR = "ASP.NET workerprocess is using wrong account. Check processModel key settings in your \"machine.config\" file";
		/// <summary>
		/// Generates signature for secret text
		/// </summary>
		/// <param name="Text">Text for signature</param>
		/// <param name="SignFile">File with signature</param>
		/// <returns></returns>
		public static string CreateSignature(string Text, string SignFile)
		{
			byte[] t, s, t20;
			string sign = "";
			int i, cnt = 0;

			// convert text to bytes
			t = System.Text.Encoding.Unicode.GetBytes(Text.ToCharArray());

			// prepare DSA cryptography
			DSACryptoServiceProvider dsa = _StartDSA(SignFile);
			try
			{
				while (cnt < t.Length || cnt == 0)
				{
					t20 = new byte[20];
					for (i = cnt; i < cnt + 20 && i < t.Length; i++)
						t20[i - cnt] = t[i];

					// encrypt part of text
					s = dsa.CreateSignature(t20);

					// convert into string represents bytes
					for (i = 0; i < s.Length; i++)
						sign += Convert.ToString(s[i], 16).PadLeft(2, '0');

					cnt += 20;
				}
			}
			finally
			{
				//HEB 20100513 CryptographyException: Keyset does not exist http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
				try
				{
					if (dsa != null) dsa.Clear();   //despite information written in blog above this may throw the same exception. But here we can catch it (in finalizer we can't)
				}
				catch (Exception ee)
				{
					Noris.Diagnostics.Trace.IgnoreError(ee);
					/*DO NOTHING */
				}
			}

			return sign;
		}

		/// <summary>
		/// Validate signature is created from text
		/// </summary>
		/// <param name="Text">Original text</param>
		/// <param name="Sign">signature for text</param>
		/// <param name="SignFile">File with signature</param>
		/// <returns></returns>
		public static bool Compare(string Text, string Sign, string SignFile)
		{
			byte[] t, s, t20, s40;
			int i, cnt = 0;

			DSACryptoServiceProvider dsa = _StartDSA(SignFile);

			try
			{


				// convert sign and text to byte array
				s = new byte[Sign.Length / 2];
				try
				{
					for (i = 0; i < Sign.Length / 2; i++)
						s[i] = (byte)Convert.ToSByte(Sign.Substring(i * 2, 2), 16);
				}
				catch (Exception ee)
				{
					Noris.Diagnostics.Trace.IgnoreError(ee);

					//throw new Exception("Bad format of encrypted text");
					return false;
				}
				if (Text == null)
					return false;

				t = System.Text.Encoding.Unicode.GetBytes(Text.ToCharArray());

				// verify signature perpartes
				if (Text.Length > 0 && (Sign == null || Sign.Length == 0)) return false;
				while (cnt < t.Length || cnt == 0)
				{
					t20 = new byte[20];
					s40 = new byte[40];
					for (i = cnt; i < cnt + 20 && i < t.Length; i++)
						t20[i - cnt] = t[i];
					for (i = 2 * cnt; i < 2 * cnt + 40 && i < s.Length; i++)
						s40[i - 2 * cnt] = s[i];

					if (!dsa.VerifySignature(t20, s40))
						return false;

					cnt += 20;
				}
			}
			finally
			{
				//HEB 20100513 CryptographyException: Keyset does not exist http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
				try
				{
					if (dsa != null) dsa.Clear();   //despite information written in blog above this may throw the same exception. But here we can catch it (in finalizer we can't)
				}
				catch (Exception ee)
				{
					Noris.Diagnostics.Trace.IgnoreError(ee);
					/*DO NOTHING */
				}
			}

			return true;
		}
		/// <summary>
		/// Encrypt Text to hexacoded string using RSA method
		/// (this is symmetric encrypt method)
		/// If SignFile is not set, default Noris sign file will be used.
		/// </summary>
		/// <param name="text">Text to encrypt</param>
		/// <returns>Hexacoded string using 0-9,a-f chars.</returns>
		public static string Encrypt(string text)
		{
			return Encrypt(text, Noris.Config.GetSignFile());
		}
		/// <param name="signFile"></param>
		public static string Encrypt(string text, string signFile)
		{
			string ret = "";
			int i, cnt = 0, max;
			byte[] b, enc;
			bool First = true;
			RSACryptoServiceProvider rsa = _StartRSA(signFile);
			try
			{

				b = ToByteArray(text);

				while (b.Length > cnt || First)
				{
					First = false;
					max = (b.Length - cnt > 117) ? 117 : b.Length - cnt;
					enc = new byte[max];
					for (i = 0; i < max; i++)
					{
						enc[i] = b[cnt];
						cnt++;
					}

					// use RSA encryption
					enc = rsa.Encrypt(enc, UseRSA);

					// convert into strig represents 
					for (i = 0; i < enc.Length; i++)
						ret += Convert.ToString(enc[i], 16).PadLeft(2, '0');
				}
			}
			finally
			{
				//HEB 20100513 CryptographyException: Keyset does not exist http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
				try
				{
					if (rsa != null) rsa.Clear();   //despite information written in blog above this may throw the same exception. But here we can catch it (in finalizer we can't)
				}
				catch (Exception ee)
				{
					Noris.Diagnostics.Trace.IgnoreError(ee);
					/*DO NOTHING */
				}
			}

			return ret;
		}

		/// <summary>
		/// Decrypt hexacoded string back to human string using RSA method
		/// (this is symmetric encrypt method). Returns null, when hexacoded string or RSA signature not valid.
		/// If SignFile is not set, default Noris sign file will be used.
		/// </summary>
		/// <param name="code">Hexacoded string what has created by Encrypt method</param>
		/// <returns></returns>
		public static string Decrypt(string code)
		{
			return Decrypt(code, Noris.Config.GetSignFile());
		}
		/// <param name="signFile"></param>
		public static string Decrypt(string Code, string signFile)
		{
			int i, cnt = 0, max;
			byte[] cpy, b = new byte[0], dec = new byte[0];

			RSACryptoServiceProvider rsa = _StartRSA(signFile);
			try
			{
				while (cnt < Code.Length / 2)
				{
					max = (Code.Length / 2 - cnt > 128) ? 128 : Code.Length / 2 - cnt;
					dec = new byte[max];
					for (i = 0; i < max; i++)
					{
						byte B;
						try
						{
							B = (byte)Convert.ToSByte(Code.Substring(cnt * 2, 2), 16);
						}
						catch (Exception ignoredException)
						{
							Noris.Diagnostics.Trace.IgnoreError(ignoredException);
							return null;
						}
						dec[i] = B;
						cnt++;
					}
					try
					{

						dec = rsa.Decrypt(dec, UseRSA);
					}
					catch (Exception ee)
					{
						Noris.Diagnostics.Trace.IgnoreError(ee);
						//throws exception when RSA Sign is corrupted
						return null;
					}

					cpy = (byte[])b.Clone();
					b = new byte[cpy.Length + dec.Length];
					cpy.CopyTo(b, 0);
					for (i = 0; i < dec.Length; i++)
						b[cpy.Length + i] = dec[i];
				}
			}
			finally
			{
				//HEB 20100513 CryptographyException: Keyset does not exist http://blogs.msdn.com/tess/archive/2007/10/31/asp-net-crash-system-security-cryptography-cryptographicexception.aspx
				try
				{
					if (rsa != null) rsa.Clear();   //despite information written in blog above this may throw the same exception. But here we can catch it (in finalizer we can't)
				}
				catch (Exception ee)
				{
					Noris.Diagnostics.Trace.IgnoreError(ee);
					/*DO NOTHING */
				}
			}

			return FromByteArray(b);
		}

		public static string DecryptString(string code, RSACryptoServiceProvider rsaProvider)
		{
			byte[] src = Convert.FromBase64String(code);
			int ln = src.Length;
			byte[] dst = new byte[ln];
			int cnt = 0, dstcnt = 0;

			while (ln > cnt || cnt == 0)
			{
				int max = (ln - cnt > 128) ? 128 : ln - cnt;
				byte[] dec = new byte[max];
				Array.Copy(src, cnt, dec, 0, max);
				cnt += max;
				dec = rsaProvider.Decrypt(dec, false);
				dec.CopyTo(dst, dstcnt);
				dstcnt += dec.Length;
			}
			return System.Text.Encoding.Unicode.GetString(dst, 0, dstcnt);//KP 7.9.2004
		}

		/// <summary>
		/// Start DSA cryptography object and set public key
		/// </summary>
		/// <returns></returns>
		private static DSACryptoServiceProvider _StartDSA(string SignFile)
		{
			return SafelyStartDSA(GetSignature(SignFile, true));
		}
		/// <summary>
		/// Returns Key Value of Noris Cryptografic Signature. If there is problem 
		/// (i.e. file not found or corrupted), returns exception.
		/// </summary>
		/// <param name="SignFile"></param>
		/// <param name="DSA">true for DSA, false for RSA</param>
		public static string GetSignature(string SignFile, bool DSA)
		{
			bool FileEx = System.IO.File.Exists(SignFile);
			if (FileEx)
			{
				string srv = FromFile(SignFile);
				int idx = srv.IndexOf("<DSAKeyValue>");
				if (idx < 0) throw new Exception("Invalid Noris cryptografic signature.");
				if (DSA) srv = srv.Substring(idx);
				else srv = srv.Substring(0, idx);
				return srv;
			}
			throw new Exception("Couldn't find Noris cryptografic signature.");
		}

		/// <summary>
		/// Starts RSA cryprografy object with our key
		/// </summary>
		/// <returns></returns>
		public static RSACryptoServiceProvider _StartRSA(string SignFile)
		{
			return SafelyStartRSA(GetSignature(SignFile, false));
		}


		private static string FromFile(string FileName)
		{
			System.IO.FileStream FF;
			try
			{
				FF = System.IO.File.OpenRead(FileName);
			}
			catch (Exception e)
			{

				throw new ApplicationException("Cryptografic information was not found.", e);
			}
			byte[] arr = null;
			try
			{
				long ln = FF.Length;
				arr = new byte[ln];
				FF.Read(arr, 0, Convert.ToInt32(ln));
			}
			finally
			{
				FF.Close();
			}
			byte offset = arr[0];
			int start = offset + 1;
			int length = (arr.Length - (2 * offset + 1)) / 2;
			byte[] res = new Byte[length];
			for (int i = 0; i < length; i++) res[i] = arr[2 * i + start];
			for (int i = 0; i < res.Length; i++)
			{
				int idx = i + 15;
				if (idx % 3 == 0) idx++;
				if (i % 5 == 1) idx += 67;
				if (i % 7 == 2) idx -= 13;
				if (i % 2 == 0) idx += 77;
				byte bb = Convert.ToByte(idx % 256);
				res[i] ^= bb;
			}
			return FromByteArray(res);
		}
		// 
		private static byte[] ToByteArray(string s)
		{
			int i;
			char[] ch = s.ToCharArray();
			ushort[] us = new ushort[ch.Length];
			byte[] b = new byte[ch.Length * 2];

			for (i = 0; i < ch.Length; i++)
				us[i] = (ushort)Convert.ChangeType(ch[i], TypeCode.UInt16);
			for (i = 0; i < us.Length; i++)
			{
				b[i * 2] = (byte)(us[i] % 16);
				b[i * 2 + 1] = (byte)(us[i] / 16);
			}
			return b;
		}
		private static string FromByteArray(byte[] b)
		{
			int i;
			string s = "";
			ushort[] us = new ushort[b.Length / 2];

			for (i = 0; i < b.Length / 2; i++)
				us[i] = (ushort)(b[i * 2] + b[i * 2 + 1] * 16);

			for (i = 0; i < us.Length; i++)
				s += (char)Convert.ChangeType(us[i], TypeCode.Char);
			return s;
		}

		#region SAFELY CREATE RSA/DSA
		/* //HEB 20090911
         * based on information from 
         * http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f
         * reason - sometimes throws System.Security.Cryptography.CryptographicException: The system cannot find the file specified
         * not sure if helps
         * 
         * */
		internal static RSACryptoServiceProvider SafelyStartRSA(string xmlExport)
		{
			bool isExport = !string.IsNullOrEmpty(xmlExport);
			RSACryptoServiceProvider rsa = null;
			try
			{
				rsa = new RSACryptoServiceProvider();
				if (isExport) rsa.FromXmlString(xmlExport);             //1ST ATTEMPT
			}
			catch (System.Security.Cryptography.CryptographicException)
			{
				try
				{
					CspParameters CSPParam = new CspParameters();
					CSPParam.Flags = CspProviderFlags.UseMachineKeyStore;
					rsa = new RSACryptoServiceProvider(CSPParam);
					if (isExport) rsa.FromXmlString(xmlExport);         //2ND ATTEMPT
				}
				catch (System.Security.Cryptography.CryptographicException)
				{
					rsa = new RSACryptoServiceProvider();
					if (isExport) rsa.FromXmlString(xmlExport);         //3RD ATTEMPT (same as the first + throw exception now
				}
			}
			return rsa;
		}

		internal static DSACryptoServiceProvider SafelyStartDSA(string xmlExport)
		{
			bool isExport = !string.IsNullOrEmpty(xmlExport);
			DSACryptoServiceProvider dsa = null;
			try
			{
				dsa = new DSACryptoServiceProvider();
				if (isExport) dsa.FromXmlString(xmlExport);             //1ST ATTEMPT
			}
			catch (System.Security.Cryptography.CryptographicException)
			{
				try
				{   //Specify 13 for DSA provider type
					CspParameters CSPParam = new CspParameters(13, null, null);
					CSPParam.Flags = CspProviderFlags.UseMachineKeyStore;
					//CSPParam.KeyContainerName = "?Your Container Name?";
					dsa = new DSACryptoServiceProvider(CSPParam);
					if (isExport) dsa.FromXmlString(xmlExport);         //2ND ATTEMPT
				}
				catch (System.Security.Cryptography.CryptographicException)
				{
					dsa = new DSACryptoServiceProvider();
					if (isExport) dsa.FromXmlString(xmlExport);         //3RD ATTEMPT (same as the first + throw exception now
				}
			}
			return dsa;
		}
		#endregion
	}
}
